package com.caigou.service.impl;

import com.alibaba.fastjson.JSON;
import com.caigou.bean.cms.entity.ArticleTitle;
import com.caigou.bean.cms.entity.EsArticle;
import com.caigou.bean.cms.param.FontMobilePram;
import com.caigou.enums.FailResultEnum;
import com.caigou.enums.ResponseEnum;
import com.caigou.mapper.cms.ChannelConfigMapper;
import com.caigou.mapper.cms.ChannelMapper;
import com.caigou.pojo.AdListMobile;
import com.caigou.pojo.ChannelDataMobile;
import com.caigou.pojo.ChannelMenuMobile;
import com.caigou.pojo.ChannelMobile;
import com.caigou.service.*;
import com.caigou.util.Result;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.Operator;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;

@Slf4j
@Service("fontMobileService")
public class FontMobileServiceImpl implements FontMobileService {

    @Resource
    private ElasticsearchTemplate elasticsearchTemplate;

    @Resource
    private ArticleService articleService;
    @Resource
    private ArticleTitleService articleTitleService;
    @Resource
    private ChannelDataService channelDataService;
    @Resource
    private ChannelMenuService channelMenuService;
    @Resource
    private AdConfigService adConfigService;

    @Resource
    private ChannelMapper channelMapper;
    @Resource
    private ChannelConfigMapper channelConfigMapper;

    private static void accept(ChannelDataMobile item) {
        item.setArticleTitle(item.getArticleTitleVOS().get(0).getArticleTitle());
    }

    @Override
    public Result loadChannelMobileList(FontMobilePram pram) {
        List<ChannelMobile> list = channelMapper.loadChannelMobileList();
        return Result.success(list);
    }

    @Override
    public Result loadChannelBannerDataMobileList(FontMobilePram pram) {
        if (null == pram.getPlateId()) return Result.error(ResponseEnum.PARAM_ERROR);
        // 根据父级ID号获取轮播图数据主动拼接020101ID号
        String plateId = pram.getPlateId() + "020101";
        List<ChannelDataMobile> list = channelDataService.loadChannelBannerDataMobileList(plateId);
        if (null != list){
            list.forEach(item -> {
                // 获取标题配置数据
                int titleType = channelConfigMapper.loadTitleTypeConfig(plateId);
                ArticleTitle articleTitle = articleTitleService.loadArticleTitle(item.getArticleId(), titleType);
                if (null == articleTitle){
                    articleTitle = articleTitleService.loadArticleTitle(item.getArticleId(), -1);
                }
                item.setArticleTitle(articleTitle.getArticleTitle());
                item.setArticleTitleImage(articleTitle.getArticleImage());
                item.setArticleTitleImageAlt(articleTitle.getArticleImageAlt());
            });
        }
        return Result.success(list);
    }

    @Override
    public Result loadChannelDataMobileList(FontMobilePram pram) {
        if (null == pram.getPlateId() && null == pram.getPlateList()) return Result.error(ResponseEnum.PARAM_ERROR);
        List<ChannelDataMobile> list;
        PageHelper.startPage(pram.getPage(), pram.getSize());
        if (null == pram.getPlateList()){// 获取单频道数据
            if (pram.getPlateId().equals("101")){
                // 获取全站数据
                list = channelDataService.loadChannelAllDataMobileList(pram.getPlateId());
            } else {
                // 获取独立频道数据
                list = channelDataService.loadChannelDataMobileList(pram.getPlateId());
            }
        } else {// 获取多频道数据
            String[] split = pram.getPlateList().split(",");
            List<String> plateIdList = Arrays.asList(split);
            list = channelDataService.loadChannelDataMoreMobileList(plateIdList);
        }
        // 如果获取的数据不为空，则处理标题/关键字数据
        if (null != list){
            list.forEach(item -> {
                // 获取标题配置数据
                ArticleTitle articleTitle = articleTitleService.loadArticleTitle(item.getArticleId(), -1);
                item.setArticleTitle(articleTitle.getArticleTitle());
                item.setArticleTitleImage(articleTitle.getArticleImage());
                item.setArticleTitleImageAlt(articleTitle.getArticleImageAlt());
            });
        }
        PageInfo<ChannelDataMobile> articleList = new PageInfo<>(list);
        return Result.success(articleList);
    }

    @Override
    public Result loadKeywordMobileList(FontMobilePram pram) {
        if (null == pram.getArticleKeyWord()) return Result.error(ResponseEnum.PARAM_ERROR);
        PageHelper.startPage(pram.getPage(), pram.getSize());
        List<String> keywords = Arrays.asList(pram.getArticleKeyWord().split(","));
        List<ChannelDataMobile> list = articleService.loadKeywordMobileList(keywords);
        // 如果获取的数据不为空，则处理标题/关键字数据
        if (null != list){
            list.forEach(item -> {
                // 获取标题配置数据
                ArticleTitle articleTitle = articleTitleService.loadArticleTitle(item.getArticleId(), -1);
                item.setArticleTitle(articleTitle.getArticleTitle());
                item.setArticleTitleImage(articleTitle.getArticleImage());
                item.setArticleTitleImageAlt(articleTitle.getArticleImageAlt());
            });
        }
        PageInfo<ChannelDataMobile> articleList = new PageInfo<>(list);
        return Result.success(articleList);
    }

    @Override
    public Result loadChannelMenuMobileList(FontMobilePram pram) {
        // 行业频道数据
        ChannelMenuMobile menu1 = new ChannelMenuMobile();
        menu1.setLabel("行业");
        menu1.setValue(0);
        List<ChannelMenuMobile> menu1Child = channelMapper.loadChannelProfessionMenu(107,111);
        menu1.setChild(menu1Child);
        // 专业频道数据
        ChannelMenuMobile menu2 = new ChannelMenuMobile();
        menu2.setLabel("专业");
        menu2.setValue(0);
        List<ChannelMenuMobile> menu2Child = channelMapper.loadChannelProfessionMenu(102,106);
        menu2.setChild(menu2Child);
        // 菜单数据
        List<ChannelMenuMobile> menu3 = channelMenuService.loadChannelMenuMobile("101","0");
        if (null != menu3){
            menu3.forEach(item -> {
                List<ChannelMenuMobile> menu3Child = channelMenuService.loadChannelMenuMobile("101",item.getMenuId().toString());
                item.setChild(menu3Child);
            });
        }
        List<ChannelMenuMobile> list = new ArrayList<>();
        list.add(menu1);
        list.add(menu2);
        assert menu3 != null;
        list.addAll(menu3);
        return Result.success(list);
    }

    @Override
    public Result loadMobileSearchList(FontMobilePram pram) {
        if (pram.getSearchType() == null) {
            return Result.error(FailResultEnum.NO_SEARCH_TYPE);
        }
        // 设置分页属性
        PageRequest page = PageRequest.of(pram.getPage() - 1, pram.getSize());
        // 1.创建QueryBuilder(即设置查询条件)这儿创建的是组合查询(也叫多条件查询),后面会介绍更多的查询方法
        BoolQueryBuilder builder = QueryBuilders.boolQuery();
        //标题汉字搜索
        if (pram.getTitle() != null && !StringUtils.isEmpty(pram.getTitle())) {
            if (pram.getSearchType() == 1){
//                builder.must(QueryBuilders.matchQuery("articleTitle", pram.getTitle()).operator(Operator.AND).boost(3));//boost默认为1
                builder.must(QueryBuilders.multiMatchQuery(pram.getTitle(), "articleTitle","articleTitleVOS.articleTitle").operator(Operator.OR).boost(1));//boost默认为1
            } else if (pram.getSearchType() == 2){
                builder.must(QueryBuilders.multiMatchQuery(pram.getTitle(), "articleTitle","articleInfo","searchTitle", "articlePureContent").operator(Operator.OR).boost(1));//boost默认为1
            }
            log.info("中文汉字搜索-->" + pram.getTitle());
        }
        //对文章类型搜索
        if (pram.getArticleType() != null) {
            if (pram.getArticleType() == 0) {// 查询全部不设置条件
                log.info("未添加搜索条件");
            } else if (pram.getArticleType() == 5) {// 只查询专题类型
                builder.must(QueryBuilders.matchQuery("articleType", 14));
            } else {// 其它查询根据传入类型进行判别
                builder.must(QueryBuilders.matchQuery("articleType", pram.getArticleType()));
            }
        }
        // 构建查询请求
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        // 设置搜索条件
        nativeSearchQueryBuilder.withQuery(builder);
        // 设置分页属性
        nativeSearchQueryBuilder.withPageable(page);
        // 设置排序属性
        if (pram.getSort() == 1) {// 相似度排序
            nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
        } else if (pram.getSort() == 2) {// 发布时间排序
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort("publicTime").order(SortOrder.DESC));
        } else {// 默认以相似度进行排序
            nativeSearchQueryBuilder.withSort(SortBuilders.scoreSort().order(SortOrder.DESC));
        }
        // 设置返回属性
        HighlightBuilder.Field highlight1 = new HighlightBuilder.Field("articleTitle");
        highlight1.preTags("<span class='high-light'>").postTags("</span>");//红色高亮标签
        HighlightBuilder.Field highlight2 = new HighlightBuilder.Field("articleTitleVOS.articleTitle");
        highlight2.preTags("<span class='high-light'>").postTags("</span>");//红色高亮标签
        nativeSearchQueryBuilder.withHighlightFields(highlight1,highlight2);
        // 设置要查询的字段
        String[] includes = {"articleId","articleType","articleTitle","articleOutLinkStatus","articleOutLink","publicTime", "articleTitleVOS"};
        FetchSourceFilter fetchSourceFilter = new FetchSourceFilter(includes, null);
        nativeSearchQueryBuilder.withSourceFilter(fetchSourceFilter);
        //生成NativeSearchQuery
        NativeSearchQuery query = nativeSearchQueryBuilder.build();
        // 解析ES回参，进行模型转换
        AggregatedPage<ChannelDataMobile> article = elasticsearchTemplate.queryForPage(query, ChannelDataMobile.class, new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
                List<T> articleList = new ArrayList<>();
                //命中记录
                SearchHits hits = response.getHits();
                for (SearchHit hit : hits) {
                    if (hits.totalHits <= 0) {// 如果没有匹配的记录则返回空出去
                        return null;
                    }
                    // 根据命中的记录转换成封装类
                    Map<String, Object> hitMap = hit.getSourceAsMap();
                    ChannelDataMobile data = parseBean(hitMap);
                    //设置高亮（若对应字段有高亮的话）
                    setHighLight(hit,"articleTitle", data);
                    setHighLight(hit,"articleTitleVOS.articleTitle", data.getArticleTitleVOS().get(0));
                    articleList.add((T) data);
                }
                return new AggregatedPageImpl<>((List<T>) articleList, pageable, response.getHits().getTotalHits());
            }
        });
        PageInfo<ChannelDataMobile> list = esArticlePage2ArticleListPage(article, pram);
        return Result.success(list);
    }

    @Override
    public Result loadMobileAdData(FontMobilePram pram) {
        if (null == pram.getAdType()) return Result.error(ResponseEnum.PARAM_ERROR);
        log.info("获取移动端广告位数据");
        List<AdListMobile> list = new ArrayList<>();
        if (pram.getAdType() == 1){// 获取移动端列表广告位
            list = adConfigService.loadMobileListAdData();
        } else if (pram.getAdType() == 2){// 获取移动端底层广告位
            list = adConfigService.loadMobileAdData();
        }
        return Result.success(list);
    }

    /**
     * ES Bean类型转换
     * @param hitMap
     * @return
     */
    private ChannelDataMobile parseBean(Map<String, Object> hitMap){
        EsArticle article = JSON.parseObject(JSON.toJSONString(hitMap), EsArticle.class);
        ChannelDataMobile data = JSON.parseObject(JSON.toJSONString(hitMap), ChannelDataMobile.class);
        // 设置标题属性
        data.setArticleTitle(article.getArticleTitleVOS().get(0).getArticleTitle());
        data.setArticleTitleImage(article.getArticleTitleVOS().get(0).getArticleImage());
        data.setArticleTitleImageAlt(article.getArticleTitleVOS().get(0).getArticleImageAlt());
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        data.setArticleTime(format.format(article.getPublicTime()));
        return data;
    }

    /**
     * ES 类转换成分页器控件
     * @param esArticlePage
     * @param param
     * @return
     */
    private PageInfo<ChannelDataMobile> esArticlePage2ArticleListPage(Page<ChannelDataMobile> esArticlePage, FontMobilePram param) {
        PageInfo<ChannelDataMobile> pageInfo = new PageInfo<>();
        //将结果转换为pageHelp分页类型结果 便于前台列表字段统一适配
        List<ChannelDataMobile> list = esArticlePage.getContent();
        list.forEach(FontMobileServiceImpl::accept);

        pageInfo.setList(list);
        pageInfo.setPageNum(esArticlePage.getNumber() + 1);
        pageInfo.setSize(esArticlePage.getSize());
        pageInfo.setTotal(esArticlePage.getTotalElements());
        pageInfo.setHasNextPage(esArticlePage.isLast());
        pageInfo.setHasPreviousPage(esArticlePage.isFirst());
        pageInfo.setIsFirstPage(param.getPage() == 1);
        pageInfo.setIsLastPage(param.getPage() <= esArticlePage.getTotalPages());
        pageInfo.setPageSize(param.getSize());
        pageInfo.setPageNum(param.getPage());
        return pageInfo;
    }

    /**
     * 设置高亮属性
     * @param hit
     * @param field
     * @param object
     */
    private static void setHighLight(SearchHit hit,String field, Object object) {
        //获取对应的高亮域
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        HighlightField highlightField = highlightFields.get(field);
        if (highlightField != null) {
            //取得定义的高亮标签
            String highLightMessage = highlightField.fragments()[0].toString();
            // 反射调用set方法将高亮内容设置进去
            try {
                String setMethodName;
                if (field.split("\\.").length > 1){
                    setMethodName = parseListSetMethodName(field);
                } else {
                    setMethodName = parseSetMethodName(field);
                }
                Class<?> Clazz = object.getClass();
                Method setMethod = Clazz.getMethod(setMethodName, String.class);
                setMethod.invoke(object, highLightMessage);
            } catch (Exception e) {
                log.error("反射报错", e);
            }
        }
    }
    private static String parseSetMethodName(String fieldName) {
        if (StringUtils.isEmpty(fieldName)) {
            return null;
        }
        int startIndex = 0;
        if (fieldName.charAt(0) == '_') {
            startIndex = 1;
        }
        return "set" + fieldName.substring(startIndex, startIndex + 1).toUpperCase()
                + fieldName.substring(startIndex + 1);
    }
    private static String parseListSetMethodName(String fieldName){
        if (StringUtils.isEmpty(fieldName) || fieldName.split("\\.").length < 2){
            return null;
        }
        String name = fieldName.split("\\.")[1];

        int startIndex = 0;
        if (name.charAt(0) == '_') {
            startIndex = 1;
        }
        return "set" + name.substring(startIndex, startIndex + 1).toUpperCase()
                + name.substring(startIndex + 1);
    }

}
